/*
 *   EuroCarbDB, a framework for carbohydrate bioinformatics
 *
 *   Copyright (c) 2006-2009, Eurocarb project, or third-party contributors as
 *   indicated by the @author tags or express copyright attribution
 *   statements applied by the authors.  
 *
 *   This copyrighted material is made available to anyone wishing to use, modify,
 *   copy, or redistribute it subject to the terms and conditions of the GNU
 *   Lesser General Public License, as published by the Free Software Foundation.
 *   A copy of this license accompanies this distribution in the file LICENSE.txt.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *   or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
 *   for more details.
 *
 *   Last commit: $Rev$ by $Author$ on $Date::             $  
 */
/**
   @author Alessio Ceroni (a.ceroni@imperial.ac.uk)
 */

package org.eurocarbdb.application.glycoworkbench.plugin;

import org.eurocarbdb.application.glycanbuilder.*;

import org.eurocarbdb.application.glycoworkbench.*;

import java.io.File;
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
import java.awt.print.PrinterJob;
import java.util.*;
import javax.swing.*;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.Range;
import org.jfree.data.xy.DefaultXYDataset;
import org.jfree.data.xy.XYDataset;
import org.jfree.chart.annotations.XYShapeAnnotation;
import org.jfree.chart.annotations.XYTextAnnotation;
import org.jfree.chart.renderer.xy.StandardXYItemRenderer;



public class SpectraPanel extends DocumentPanel<SpectraDocument> implements ActionListener, MouseListener, MouseMotionListener {        

	public interface SelectionChangeListener {    
		public void selectionChanged(SelectionChangeEvent e);    
	}

	public static class SelectionChangeEvent {
		private SpectraPanel src;

		public SelectionChangeEvent(SpectraPanel _src) {
			src = _src;
		}

		public SpectraPanel getSource() {
			return src;
		}       
	}

	// components
	protected AnnotatedPeakList theSearchResults;
	protected DefaultXYDataset theDataset;
	protected DefaultXYDataset theIsotopesDataset;
	protected XYPlot      thePlot;
	protected JFreeChart  theChart;
	protected ChartPanel  theChartPanel;

	protected JToolBar    theToolBar;
	protected JToolBar    theSecondToolBar;

	// data
	protected int current_ind;
	protected TreeMap<Double,Double> visibleData;

	// selection
	protected Peak current_peak;
	protected TreeSet<Peak> selected_peaks;

	// actions    
	protected String shown_mslevel;    
	protected JButton mslevel_button;

	protected boolean was_moving;
	protected boolean is_moving;
	protected Point2D mouse_start_point;
	protected Rectangle2D zoom_rectangle;    
	protected Cursor hand_cursor;

	protected GlycanAction ms_action;
	protected GlycanAction msms_action;

	protected boolean update_isotope_curves;
	protected boolean automatic_update_isotope_curves;
	protected boolean show_all_isotopes;
	protected JButton isotopes_button;
	protected JButton ftmode_button;

	// 
	protected Vector<SelectionChangeListener> listeners;

	//

	public SpectraPanel()  {
		super();
	}

	protected void initSingletons() {
		super.initSingletons();

		theSearchResults=null;

		selected_peaks = new TreeSet<Peak>();
		shown_mslevel = "msms";    
		current_ind = 0;
		visibleData = new TreeMap<Double,Double>();    

		update_isotope_curves = false;
		automatic_update_isotope_curves = true;
		show_all_isotopes = false;
	}

	protected void initComponents() {
		setLayout(new BorderLayout());

		// create chart
		theDataset = new DefaultXYDataset();
		theIsotopesDataset = new DefaultXYDataset();

		theChart = org.jfree.chart.ChartFactory.createScatterPlot("Spectrum", "m/z ratio", "Intensity", theDataset, org.jfree.chart.plot.PlotOrientation.VERTICAL, false, false, false);
		thePlot = (XYPlot)theChart.getPlot();
		thePlot.setRenderer(new StandardXYItemRenderer(StandardXYItemRenderer.LINES));

		thePlot.setDataset(1, theIsotopesDataset);
		thePlot.setRenderer(1,new StandardXYItemRenderer(StandardXYItemRenderer.LINES));

		theChartPanel = new ChartPanel(theChart);    
		theChartPanel.setDomainZoomable(false);
		theChartPanel.setRangeZoomable(false);    
		theChartPanel.setPopupMenu(null);
		
		// create toolbar
		theToolBar = createToolBar(); 
		add(theToolBar, BorderLayout.NORTH);

		//TODO: PAINT-RESTORE
		add(theChartPanel,BorderLayout.CENTER);
		
		theSecondToolBar =createSecondToolBar();
		add(theSecondToolBar, BorderLayout.SOUTH);
	}

	protected void finalSettings() {

		// load cursors
		hand_cursor = FileUtils.createCursor("hand");    

		// add listeners
		listeners = new Vector<SelectionChangeListener>();

		thePlot.getDomainAxis().addChangeListener(new org.jfree.chart.event.AxisChangeListener() {
			public void axisChanged(org.jfree.chart.event.AxisChangeEvent event) {
				updateChart();
			}
		});

		theChartPanel.addMouseMotionListener( this );
		theChartPanel.addMouseListener( this );

		was_moving = false;
		is_moving = false;
		mouse_start_point = null;
		zoom_rectangle = null;    

		super.finalSettings();
	}   

	public SpectraDocument getDocumentFromWorkspace(GlycanWorkspace workspace) {
		return ( workspace!=null ) ?workspace.getSpectra() :null;
	}


	public void setDocumentFromWorkspace(GlycanWorkspace workspace) {
		if( theDocument!=null ) 
			theDocument.removeDocumentChangeListener(this);
		if( theSearchResults!=null )
			theSearchResults.removeDocumentChangeListener(this);

		theDocument = getDocumentFromWorkspace(workspace);
		if( theDocument==null )
			theDocument = new SpectraDocument(workspace.getCurrentScan());
		
		theSearchResults = (workspace!=null) ?workspace.getSearchResults() : null;

		theDocument.addDocumentChangeListener(this);
		if( theSearchResults!=null )
			theSearchResults.addDocumentChangeListener(this);

		current_ind = 0;    
		updateView();
		updateActions();
	}     


	protected void createActions() {
		theActionManager.add("mslevel=ms",FileUtils.defaultThemeManager.getImageIcon("msms"),"Change current scan level",-1, "",this);
		theActionManager.add("mslevel=msms",FileUtils.defaultThemeManager.getImageIcon("ms"),"Change current scan level",-1, "",this);

		theActionManager.add("updateisotopecurves=true",FileUtils.defaultThemeManager.getImageIcon("isotopesoff"),"Automatic computation of isotopic distributions inactive",-1, "",this);
		theActionManager.add("updateisotopecurves=false",FileUtils.defaultThemeManager.getImageIcon("isotopeson"),"Automatic computation of isotopic distributions active",-1, "",this);

		theActionManager.add("showallisotopes=true",FileUtils.defaultThemeManager.getImageIcon("ftmodeoff"),"FTICR mode inactive",-1, "",this);
		theActionManager.add("showallisotopes=false",FileUtils.defaultThemeManager.getImageIcon("ftmodeon"),"FTICR mode active",-1, "",this);

		theActionManager.add("close",FileUtils.defaultThemeManager.getImageIcon("close"),"Close structure",KeyEvent.VK_S, "",this);
		theActionManager.add("previous",FileUtils.defaultThemeManager.getImageIcon("previous"),"Previous structure",KeyEvent.VK_L, "",this);
		theActionManager.add("next",FileUtils.defaultThemeManager.getImageIcon("next"),"Next structure",KeyEvent.VK_N, "",this);

		theActionManager.add("edit",FileUtils.defaultThemeManager.getImageIcon("edit"),"Edit scan data",-1, "",this);

		theActionManager.add("new",GlycoWorkbench.getDefaultThemeManager().getResizableIcon(STOCK_ICON.REFRESH, Plugin.DEFAULT_ICON_SIZE),"Clear",KeyEvent.VK_N, "",this);
		theActionManager.add("openspectra",GlycoWorkbench.getDefaultThemeManager().getResizableIcon(STOCK_ICON.DOCUMENT_OPEN, Plugin.DEFAULT_ICON_SIZE),"Open Spectra",KeyEvent.VK_O, "",this);

		theActionManager.add("print",GlycoWorkbench.getDefaultThemeManager().getResizableIcon(STOCK_ICON.DOCUMENT_PRINT, Plugin.DEFAULT_ICON_SIZE),"Print...",KeyEvent.VK_P, "",this);

		theActionManager.add("addpeaks",FileUtils.defaultThemeManager.getImageIcon("addpeaks"),"Add selected peaks to list",-1, "",this);
		theActionManager.add("annotatepeaks",FileUtils.defaultThemeManager.getImageIcon("annotatepeaks"),"Find possible annotations for selected peaks",-1, "",this);
		theActionManager.add("baselinecorrection",FileUtils.defaultThemeManager.getImageIcon("baseline"),"Baseline correction of current spectrum", -1, "", this);
		theActionManager.add("noisefilter",FileUtils.defaultThemeManager.getImageIcon("noisefilter"),"Filter noise in current spectrum", -1, "", this);
		theActionManager.add("centroid",FileUtils.defaultThemeManager.getImageIcon("centroid"),"Compute peak centroids",-1, "",this);

		theActionManager.add("arrow",FileUtils.defaultThemeManager.getImageIcon("arrow"),"Activate zoom",-1, "",this);
		theActionManager.add("hand",FileUtils.defaultThemeManager.getImageIcon("hand"),"Activate moving",-1, "",this);

		theActionManager.add("zoomnone",FileUtils.defaultThemeManager.getImageIcon("zoomnone"),"Reset zoom",-1, "",this);
		theActionManager.add("zoomin",FileUtils.defaultThemeManager.getImageIcon("zoomin"),"Zoom in",-1, "",this);
		theActionManager.add("zoomout",FileUtils.defaultThemeManager.getImageIcon("zoomout"),"Zoom out",-1, "",this);
	}

	protected void updatePeakActions() {
		if( ms_action==null && theApplication.getPluginManager().getMsPeakActions().size()>0 ) 
			ms_action = theApplication.getPluginManager().getMsPeakActions().iterator().next();

		if( msms_action==null && theApplication.getPluginManager().getMsMsPeakActions().size()>0 ) 
			msms_action = theApplication.getPluginManager().getMsMsPeakActions().iterator().next();
	}

	protected void updateActions() {
		theActionManager.get("close").setEnabled(theDocument.getNoScans()!=0);
		theActionManager.get("previous").setEnabled(current_ind>0);
		theActionManager.get("next").setEnabled(current_ind<(theDocument.getNoScans()-1));

		theActionManager.get("arrow").setEnabled(is_moving);
		theActionManager.get("hand").setEnabled(!is_moving);

		theActionManager.get("edit").setEnabled(theDocument.getNoScans()>0);

		theActionManager.get("zoomnone").setEnabled(theDocument.getNoScans()>0);
		theActionManager.get("zoomin").setEnabled(theDocument.getNoScans()>0);
		theActionManager.get("zoomout").setEnabled(theDocument.getNoScans()>0);

		theActionManager.get("addpeaks").setEnabled(selected_peaks.size()>0);
		theActionManager.get("annotatepeaks").setEnabled(selected_peaks.size()>0);

		theActionManager.get("baselinecorrection").setEnabled(theDocument.getNoScans()!=0);
		theActionManager.get("noisefilter").setEnabled(theDocument.getNoScans()!=0);
		theActionManager.get("centroid").setEnabled(theDocument.getNoScans()!=0);

		updateMsLevel();
	}

	protected void updateData() {
	}

	private JToolBar createToolBar() {
		JToolBar toolbar = new JToolBar();
		toolbar.setFloatable(false);

		toolbar.add(theActionManager.get("previous"));
		toolbar.add(theActionManager.get("close"));    
		toolbar.add(theActionManager.get("next"));

		toolbar.addSeparator();

		toolbar.add(theActionManager.get("new"));
		toolbar.add(theActionManager.get("openspectra"));
		toolbar.add(theActionManager.get("edit"));

		toolbar.addSeparator();

		toolbar.add(theActionManager.get("print"));

		toolbar.addSeparator();

		toolbar.add(theActionManager.get("arrow"));
		toolbar.add(theActionManager.get("hand"));

		toolbar.addSeparator();

		toolbar.add(theActionManager.get("zoomnone"));
		toolbar.add(theActionManager.get("zoomin"));
		toolbar.add(theActionManager.get("zoomout"));

		return toolbar;
	}            
	
	private JToolBar createSecondToolBar() {
		JToolBar toolbar = new JToolBar();
		toolbar.setFloatable(false);

		toolbar.addSeparator();

		toolbar.add(mslevel_button = new JButton(theActionManager.get("mslevel=msms")) );
		mslevel_button.setText(null);
		toolbar.add(theActionManager.get("addpeaks"));
		toolbar.add(theActionManager.get("annotatepeaks"));

		toolbar.addSeparator();

		toolbar.add(theActionManager.get("noisefilter"));
		toolbar.add(theActionManager.get("baselinecorrection"));
		toolbar.add(theActionManager.get("centroid"));
		toolbar.add(isotopes_button = new JButton(theActionManager.get("updateisotopecurves=false")) );
		isotopes_button.setText(null);
		toolbar.add(ftmode_button = new JButton(theActionManager.get("showallisotopes=true")) );
		ftmode_button.setText(null);

		return toolbar;
	}            

	private JPopupMenu createPopupMenu(boolean over_peak) {

		JPopupMenu menu = new JPopupMenu();

		if( over_peak ) {
			updatePeakActions();

			menu.add(theActionManager.get("addpeaks"));

			ButtonGroup group = new ButtonGroup();    
			if( shown_mslevel.equals("ms") ) {
				for( GlycanAction a: theApplication.getPluginManager().getMsPeakActions() ) {
					JRadioButtonMenuItem last = new JRadioButtonMenuItem(new GlycanAction(a,"annotatepeaks",-1,"",this));

					menu.add(last);
					last.setSelected(a == ms_action);
					group.add(last);
				}
			}
			else {
				for( GlycanAction a: theApplication.getPluginManager().getMsMsPeakActions() ) {
					JRadioButtonMenuItem last = new JRadioButtonMenuItem(new GlycanAction(a,"annotatepeaks",-1,"",this));

					menu.add(last);
					last.setSelected(a == msms_action);
					group.add(last);
				}
			}
			menu.addSeparator();
		}

		menu.add(theActionManager.get("zoomnone"));
		menu.add(theActionManager.get("zoomin"));
		menu.add(theActionManager.get("zoomout"));

		return menu;
	}        

	//-----------
	// Visualization

	public double screenToDataX(double length) {
		Rectangle2D data_area = theChartPanel.getScreenDataArea();
		double mz_unit  = thePlot.getDomainAxis().lengthToJava2D(1.,data_area,thePlot.getDomainAxisEdge());
		return length/mz_unit;
	}

	public double screenToDataY(double length) {
		Rectangle2D data_area = theChartPanel.getScreenDataArea();
		double int_unit  = thePlot.getRangeAxis().lengthToJava2D(1.,data_area,thePlot.getRangeAxisEdge());
		return length/int_unit;
	}

	public Point2D screenToDataCoords(Point2D p) {

		Rectangle2D data_area = theChartPanel.getScreenDataArea();
		double x = thePlot.getDomainAxis().java2DToValue(p.getX(),data_area,thePlot.getDomainAxisEdge());
		double y = thePlot.getRangeAxis().java2DToValue(p.getY(),data_area,thePlot.getRangeAxisEdge());
		return new Point2D.Double(x,y);
	}


	public double screenToDataCoordX(double x) {
		Rectangle2D data_area = theChartPanel.getScreenDataArea();
		return thePlot.getDomainAxis().java2DToValue(x,data_area,thePlot.getDomainAxisEdge());
	}

	public double screenToDataCoordY(double y) {
		Rectangle2D data_area = theChartPanel.getScreenDataArea();
		return thePlot.getRangeAxis().java2DToValue(y,data_area,thePlot.getRangeAxisEdge());
	}

	public void updateMZAxis() {
		if( theDocument.getNoScans()>0 ) 
			thePlot.getDomainAxis().setRange(theDocument.getPeakDataAt(current_ind).getMZRange());    
		else
			thePlot.getDomainAxis().setRange(new Range(0.,1.));
	}

	public void updateIntensityAxis() {
		double max_int = 0.;
		for( int d=0; d<thePlot.getDatasetCount(); d++ ) {
			XYDataset dataset = thePlot.getDataset(d);
			for( int s=0; s<dataset.getSeriesCount(); s++ )
				for( int i=0; i<dataset.getItemCount(s); i++ )
					max_int = Math.max(max_int,dataset.getYValue(s,i));                   
		} 

		if( max_int==0. ) {
			// no data
			return;
		}
		Range new_int_range = new Range(0.,max_int);

		// make space for annotations
		Rectangle2D data_area = theChartPanel.getScreenDataArea();        
		if( data_area.getHeight()>0 ) 
			new_int_range = Range.expand(new_int_range,0.,12. / data_area.getHeight());
		thePlot.getRangeAxis().setRange(new_int_range);
	}

	public void updateChart() {
		// auto zoom
		if( theDocument.getNoScans()>0 ) {        
			Range mz_range  = thePlot.getDomainAxis().getRange();

			// update data
			double mz_toll = screenToDataX(1.);
			double[][] data = theDocument.getPeakDataAt(current_ind).getData(mz_range,mz_toll);

			// update visible data and compute intensity range
			visibleData.clear();
			double min_int = (data[0].length>0) ?data[1][0] :0.;
			double max_int = (data[0].length>0) ?data[1][0] :0.;
			for( int i=0; i<data[0].length; i++ ) {
				min_int = Math.min(min_int,data[1][i]);
				max_int = Math.max(max_int,data[1][i]);
				visibleData.put(data[0][i],data[1][i]);
			}

			//Range new_int_range = new Range(min_int,max_int);
			Range new_int_range = new Range(0.,max_int);

			// make space for annotations
			Rectangle2D data_area = theChartPanel.getScreenDataArea();        
			if( data_area.getHeight()>0 ) 
				new_int_range = Range.expand(new_int_range,0.,12. / data_area.getHeight());

			// resize y axis
			thePlot.getRangeAxis().setRange(new_int_range);

			// reload dataset
			//theDataset.removeSeries("intensities");
			theDataset.addSeries("intensities",data);

			/*
        for( int i=0; i<theDataset.getSeriesCount(); i++ ) {
        if( theDataset.getSeriesKey(i).equals("intensities") )
            thePlot.getRenderer().setSeriesPaint(i,Color.red);                    
        else
            thePlot.getRenderer().setSeriesPaint(i,Color.blue);
        }
			 */
		}
		else {
			thePlot.getRangeAxis().setRange(new Range(0.,1.));
			for( int i=0; i<theDataset.getSeriesCount(); i++ )
				theDataset.removeSeries(theDataset.getSeriesKey(i));
		}


		// restore annotation shapes
		showSelection();
	}

	public void updateTitle() {
		if( theDocument.getNoScans()>0 ) {
			String title = "Spectrum " + theDocument.getScanNum(current_ind) + "/" + theDocument.getNoScans();

			int ms_level;
			if( shown_mslevel.equals("ms") ) 
				ms_level = 1;
			else {
				ms_level = theDocument.getScanDataAt(current_ind).getMSLevel();
				if( ms_level==1 )
					ms_level = 2;
			}

			System.out.println("MS Level="+ms_level);
			
			if( ms_level==1 ) 
				title += ", MS";
			else if( ms_level==2 ) 
				title += ", MS/MS";
			else if( ms_level>2 ) 
				title += ", MS" + ms_level;

			if(ms_level>1){
				Double precursor=theDocument.getScanDataAt(current_ind).getPrecursorMZ();
				String precursorStr="?";
				if(precursor!=null && precursor!=Double.NaN){
					precursor=new Double(0.0);
					precursorStr=new java.text.DecimalFormat("0.0000").format(precursor);
				}
				
				System.out.println("Precursor="+precursorStr);
				
				title += ", precursor= " + precursorStr + " Da";
			}
				
				

			theChart.setTitle(title);
		}
		else {
			theChart.setTitle("Spectrum");
		}
	}

	public void updateMsLevel() {
		if( theWorkspace.getCurrentScan()!=null )
			onSetMsLevel(theWorkspace.getCurrentScan().isMsMs()?"msms" :"ms",false);
		else
			onSetMsLevel("msms",false);
	}


	public void updateView() {

		current_ind = Math.max(0,Math.min(current_ind,theDocument.getNoScans()-1));

		// clear selection
		resetSelection();

		// update title
		updateMZAxis();
		updateChart();
		updateTitle();
	}   

	//--------------
	// manage selection

	private Peak findNearestPeak(double mz, double intensity, double mz_toll, double int_toll) {    
		java.util.SortedMap<Double,Double> submap = visibleData.subMap(mz-mz_toll,mz+mz_toll);

		Peak found = null;
		double min_diff = 0.;
		for( Map.Entry<Double,Double> e : submap.entrySet() ) { 
			double diff = Math.abs(e.getValue()-intensity);
			if( diff<=int_toll ) {
				if( found==null || diff<min_diff ) 
					found = new Peak(e.getKey(),e.getValue());        
			}
		}
		return found;
	}

	public Peak findPeakAt(Point2D p) {

		if( theDocument.getNoScans()>0 ) {        
			Point2D dp = screenToDataCoords(p);
			double mz_toll  =  screenToDataX(3.);
			double int_toll =  screenToDataY(3.);
			return findNearestPeak(dp.getX(),dp.getY(),mz_toll,int_toll);
		}
		return null;
	}

	public boolean hasSelection() {
		return (selected_peaks.size()>0);
	}

	public boolean isSelected(Peak peak) {
		return (peak==null) ?false :selected_peaks.contains(peak);
	}

	public void resetSelection() {
		current_peak = null;
		selected_peaks.clear();
		fireUpdatedSelection();
	}

	public void setSelection(Peak peak) {
		if( peak!=null ) {
			selected_peaks.clear();

			selected_peaks.add(peak);
			current_peak = peak;

			fireUpdatedSelection();        
		}
		else
			resetSelection();
	}


	public void addSelection(Peak peak) {
		if( peak!=null ) {
			if( !isSelected(peak) ) 
				selected_peaks.add(peak);        
			else
				current_peak = peak;
			fireUpdatedSelection();        
		}
	}

	public void enforceSelection(Peak peak) {
		if( peak!=null ) {
			if( !isSelected(peak) ) 
				setSelection(peak);        
			else {
				current_peak = peak;
				fireUpdatedSelection();
			}
		}
		else
			resetSelection();    
	}

	private void showSelection() {    
		thePlot.clearAnnotations();
		if( selected_peaks.size()>0 ) {        
			double width = screenToDataX(6.);
			double height = screenToDataY(6.);
			double margin = screenToDataY(8.);
			for( Peak p : selected_peaks ) {
				// add annotation
				Shape shape = new Rectangle2D.Double(p.getMZ()-width/2.,p.getIntensity()-height/2.,width,height);
				if( p.equals(current_peak) ) {
					thePlot.addAnnotation( new XYShapeAnnotation(shape,new BasicStroke(2.f),Color.blue) );    
					thePlot.addAnnotation( new XYTextAnnotation(new java.text.DecimalFormat("0.0000").format(p.getMZ()), p.getMZ(), p.getIntensity() + margin ) );    
				}
				else
					thePlot.addAnnotation( new XYShapeAnnotation(shape,new BasicStroke(1.f),Color.black) );    
			}
		}
	}

	//-----------------
	// actions    

	public int getCurrentInd() {
		return current_ind;
	}

	public void closeCurrent() {
		if( theDocument.getNoScans()>0 ) {
			int old_ind = current_ind;
			current_ind = Math.min(current_ind,theDocument.getNoScans()-2);
			current_ind = Math.max(current_ind,0);

			theDocument.removeScan(old_ind);

			updateActions();
			updateView();
		}
	}

	public void showPrevious() {
		if( theDocument.getNoScans()>0 && current_ind>0 ) {
			current_ind--;
			updateView();
		}
	}

	public void showNext() {
		if( theDocument.getNoScans()>0 && current_ind<(theDocument.getNoScans()-1) ) {
			current_ind++;
			updateView();
		}
	}      

	public void showScan(int s_ind) {
		if( s_ind>=0 && s_ind<theDocument.getNoScans() ) {
			current_ind = s_ind;
			updateView();
		}
	}    

	public void onEditScanData() {
		if( theDocument.getNoScans()>0 )
			new ScanDataPropertiesDialog(theApplication,theDocument.getScanDataAt(current_ind), theWorkspace).setVisible(true);

	}

	public void onPrint() {
		PrinterJob pj = theWorkspace.getPrinterJob();
		if( pj==null )
			return;

		try {            
			pj.setPrintable(theChartPanel);
			if( pj.printDialog() ) 
				pj.print();
		}
		catch(Exception e) {
			LogUtils.report(e);
		}
	}

	public void onActivateZooming() {
		is_moving = false;
		theChartPanel.setCursor(Cursor.getDefaultCursor());        
	}

	public void onActivateMoving() {
		is_moving = true;
		//theChartPanel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
		theChartPanel.setCursor(hand_cursor);
	}

	public void onZoomNone() {
		if( theDocument.getNoScans()>0 )
			thePlot.getDomainAxis().setRange(theDocument.getPeakDataAt(current_ind).getMZRange()); 
	}

	public void onZoomIn() {
		if( theDocument.getNoScans()>0 )
			thePlot.getDomainAxis().resizeRange(0.5);
	}

	public void onZoomOut() {
		if( theDocument.getNoScans()>0 ) {
			thePlot.getDomainAxis().resizeRange(2.0);
			if( thePlot.getDomainAxis().getRange().contains(theDocument.getPeakDataAt(current_ind).getMinMZ()) ||
					thePlot.getDomainAxis().getRange().contains(theDocument.getPeakDataAt(current_ind).getMaxMZ()) )
				thePlot.getDomainAxis().setRange(theDocument.getPeakDataAt(current_ind).getMZRange());
		}
	}

	public void onSetMsLevel(String mslevel, boolean changedoc) {
		shown_mslevel = mslevel;
		if( mslevel.equals("ms") ) {
			mslevel_button.setAction(theActionManager.get("mslevel=msms"));
			mslevel_button.setText(null);
			if( changedoc )
				theWorkspace.setMsMs(theWorkspace.getCurrentScan(),false);
		}
		else {
			mslevel_button.setAction(theActionManager.get("mslevel=ms"));
			mslevel_button.setText(null);
			if( changedoc )
				theWorkspace.setMsMs(theWorkspace.getCurrentScan(),true);
		}
		updateTitle();
	}

	public void onSetUpdateIsotopeCurves(Boolean f) {
		automatic_update_isotope_curves = f;

		if( automatic_update_isotope_curves ) 
			isotopes_button.setAction(theActionManager.get("updateisotopecurves=false"));
		else
			isotopes_button.setAction(theActionManager.get("updateisotopecurves=true"));
		isotopes_button.setText(null);
	}

	public void onSetShowAllIsotopes(Boolean f) {
		show_all_isotopes = f;

		if( show_all_isotopes ) 
			ftmode_button.setAction(theActionManager.get("showallisotopes=false"));
		else
			ftmode_button.setAction(theActionManager.get("showallisotopes=true"));
		ftmode_button.setText(null);
	}

	public boolean onAddPeaks() {
		try {
			if( selected_peaks.size()>0 ) {
				// add selected peaks to list
				theApplication.getWorkspace().getPeakList().mergeData(selected_peaks);
				theApplication.getPluginManager().show("PeakList","PeakList");
				return true;
			}
			return false;
		}
		catch(Exception e) { 
			LogUtils.report(e);
			return false;
		}
	}

	public boolean onAnnotatePeaks(String parent_action) {
		try {
			if( selected_peaks.size()>0 ) {
				updatePeakActions();

				if( shown_mslevel.equals("ms") ) {
					if( parent_action!=null )            
						ms_action = theApplication.getPluginManager().getMsPeakAction(parent_action);            
					if( theApplication.getPluginManager().runAction(ms_action,new PeakList(selected_peaks)) ) {
						update_isotope_curves = automatic_update_isotope_curves;
						return true;
					}
					return false;
				}

				if( parent_action!=null )            
					msms_action = theApplication.getPluginManager().getMsMsPeakAction(parent_action);            
				if( theApplication.getPluginManager().runAction(msms_action,new PeakList(selected_peaks)) ) {
					update_isotope_curves = automatic_update_isotope_curves;
					return true;
				}
				return false;
			}
			return false;
		}
		catch(Exception e) {  
			LogUtils.report(e);
			return false;
		}
	}

	private void removeIsotopeCurves() {
		while( theIsotopesDataset.getSeriesCount()>0 )
			theIsotopesDataset.removeSeries(theIsotopesDataset.getSeriesKey(0));
	}

	private void updateIsotopeCurves() {    
		update_isotope_curves = false;    

		TreeMap<Peak,Collection<Annotation>> map = new TreeMap<Peak,Collection<Annotation>>();
		for( PeakAnnotationMultiple pam : theSearchResults.getAnnotations() ) {
			if( map.get(pam.getPeak())==null )
				map.put(pam.getPeak(),new Vector<Annotation>());

			Collection<Annotation> dest = map.get(pam.getPeak());
			for( Vector<Annotation> va : pam.getAnnotations() )
				dest.addAll(va);
		}    
		addIsotopeCurves(map);
	}      


	public void addIsotopeCurves(TreeMap<Peak,Collection<Annotation>> annotations) {

		if( theDocument.size()==0 )
			return;

		// remove old curves
		removeIsotopeCurves();

		// add curves
		if( annotations!=null ) {

			// set renderer
			if( show_all_isotopes ) {
				thePlot.setRenderer(1,new StandardXYItemRenderer(StandardXYItemRenderer.SHAPES));
				thePlot.getRenderer(1).setShape(new Ellipse2D.Double(0,0,7,7));
			}
			else
				thePlot.setRenderer(1,new StandardXYItemRenderer(StandardXYItemRenderer.LINES));

			MSUtils.IsotopeList isotope_list = new MSUtils.IsotopeList(show_all_isotopes);
			for(Map.Entry<Peak,Collection<Annotation>> pa : annotations.entrySet() ) {
				Peak p = pa.getKey();
				double[] best_peak = theDocument.getPeakDataAt(current_ind).findNearestPeak(p.getMZ());

				// get compositions
				HashSet<Molecule> compositions = new HashSet<Molecule>();    
				for( Annotation a : pa.getValue() ) {
					try { 
						compositions.add(a.getFragmentEntry().fragment.computeIon());    
					}
					catch(Exception e) {
						e.printStackTrace();
					}
				}

				// collect curves for this peak
				HashMap<String,double[][]> all_curves = new HashMap<String,double[][]>();
				for( Molecule m : compositions ) {
					try {
						double[][] data = MSUtils.getIsotopesCurve(1,m,show_all_isotopes);

						// overlay the distribution with the existing list of isotopes
						isotope_list.adjust(data,best_peak[0],best_peak[1]);             

						all_curves.put(m.toString(),data);
					}
					catch(Exception e) {
						LogUtils.report(e);
					}
				}    

				// add average curve for this peak
				if( all_curves.size()>1 ) {
					double[][] data = MSUtils.average(all_curves.values(),show_all_isotopes);

					// add the average to the chart
					String name = "average-"+p.getMZ();
					theIsotopesDataset.addSeries(name, data);    
					thePlot.getRenderer(1).setSeriesPaint(theIsotopesDataset.indexOf(name),Color.magenta);
					thePlot.getRenderer(1).setSeriesStroke(theIsotopesDataset.indexOf(name),new BasicStroke(2));            

					// add the average to the isotope list
					isotope_list.add(data,false);
				}
				else if( all_curves.size()==1 ) {
					// add the only curve to the isotope list
					isotope_list.add(all_curves.values().iterator().next(),false); 
				} 

				// add the other curves
				for( Map.Entry<String,double[][]> e : all_curves.entrySet() ) {
					String name = e.getKey() + "-" + p.getMZ();
					theIsotopesDataset.addSeries(name, e.getValue());    
					thePlot.getRenderer(1).setSeriesPaint(theIsotopesDataset.indexOf(name),Color.blue);            
				}
			}        

		}    
		updateIntensityAxis();    
	}


	public void noiseFilter() {
		theDocument.noiseFilter(current_ind);
	}

	public void baselineCorrection() {
		theDocument.baselineCorrection(current_ind);
	}


	public void computeCentroids() {
		((SpectraPlugin)theApplication.getPluginManager().get("Spectra")).computeCentroids(current_ind);
	}



	//-----------
	// listeners

	public void addSelectionChangeListener(SelectionChangeListener l) {
		if( l!=null )
			listeners.add(l);
	}

	public void removeSelectionChangeListener(SelectionChangeListener l) {
		if( l!=null )
			listeners.remove(l);
	}

	public void fireUpdatedSelection() {
		for( SelectionChangeListener l : listeners ) 
			l.selectionChanged(new SelectionChangeEvent(this));
		removeIsotopeCurves();
		updateIntensityAxis();
		showSelection();
		updateActions();
	}

	public void actionPerformed(ActionEvent e) {

		String action = GlycanAction.getAction(e);
		String param  = GlycanAction.getParam(e);    

		if( action.equals("previous") )
			showPrevious();
		else if( action.equals("next") )
			showNext();
		else if( action.equals("close") )
			closeCurrent();

		else if( action.equals("new") )
			theApplication.onNew(theDocument);
		else if( action.equals("openspectra") )
			theApplication.onOpen(null,theDocument,false);

		else if( action.equals("edit") )
			onEditScanData();

		else if( action.equals("print") )
			onPrint();

		else if( action.equals("arrow") )
			onActivateZooming();
		else if( action.equals("hand") )
			onActivateMoving();

		else if( action.equals("zoomnone") )
			onZoomNone();
		else if( action.equals("zoomin") )
			onZoomIn();
		else if( action.equals("zoomout") )
			onZoomOut();

		else if( action.equals("mslevel") ) 
			onSetMsLevel(param,true);
		else if( action.equals("addpeaks") )
			onAddPeaks();
		else if( action.equals("annotatepeaks") )
			onAnnotatePeaks(param);
		else if( action.equals("updateisotopecurves") ) 
			onSetUpdateIsotopeCurves(Boolean.valueOf(param));
		else if( action.equals("showallisotopes") ) 
			onSetShowAllIsotopes(Boolean.valueOf(param));
		else if( action.equals("noisefilter") )
			noiseFilter();
		else if( action.equals("baselinecorrection") )
			baselineCorrection();
		else if( action.equals("centroid") )
			computeCentroids();

		updateActions();
	}

	public void mouseEntered(MouseEvent e) {
	}

	public void mouseExited(MouseEvent e) {
	}

	public void mousePressed(MouseEvent e) {

		if( MouseUtils.isPopupTrigger(e) ) {
			// open popup
			current_peak = findPeakAt(e.getPoint());
			enforceSelection(current_peak);        
			createPopupMenu(current_peak!=null).show(theChartPanel, e.getX(), e.getY()); 
		}
		else {
			was_moving = is_moving;
			if( (MouseUtils.isPushTrigger(e) || MouseUtils.isMoveTrigger(e) ) && theChartPanel.getScreenDataArea().contains(e.getPoint()) ) {
				mouse_start_point = e.getPoint();
				if( MouseUtils.isMoveTrigger(e) ) 
					onActivateMoving();                
			}
		}
	}        

	public void mouseMoved(MouseEvent e) {
	}

	public void mouseDragged(MouseEvent e) {
		if( mouse_start_point!=null && theDocument.getNoScans()>0 ) {
			if( is_moving ) {
				// moving
				double mz_delta = screenToDataX(mouse_start_point.getX() - e.getPoint().getX());
				if( mz_delta>0. ) {
					double old_upper_bound = thePlot.getDomainAxis().getUpperBound();
					double old_lower_bound = thePlot.getDomainAxis().getLowerBound();
					double new_upper_bound = Math.min(old_upper_bound+mz_delta,theDocument.getPeakDataAt(current_ind).getMaxMZ());
					double new_lower_bound = old_lower_bound + new_upper_bound - old_upper_bound;

					thePlot.getDomainAxis().setRange(new Range(new_lower_bound,new_upper_bound));
				}
				else {
					double old_upper_bound = thePlot.getDomainAxis().getUpperBound();
					double old_lower_bound = thePlot.getDomainAxis().getLowerBound();
					double new_lower_bound = Math.max(old_lower_bound+mz_delta,theDocument.getPeakDataAt(current_ind).getMinMZ());
					double new_upper_bound = old_upper_bound + new_lower_bound - old_lower_bound;

					thePlot.getDomainAxis().setRange(new Range(new_lower_bound,new_upper_bound));
				}

				mouse_start_point = e.getPoint();
			}    
			else {
				// zooming                
				Graphics2D g2 = (Graphics2D) theChartPanel.getGraphics();
				g2.setXORMode(java.awt.Color.gray);

				// delete old rectangle
				if( zoom_rectangle!=null) 
					g2.draw(zoom_rectangle);

				// create new rectangle
				double start_x = Math.min(e.getX(),mouse_start_point.getX());
				double end_x = Math.max(e.getX(),mouse_start_point.getX());

				Rectangle2D data_area = theChartPanel.getScreenDataArea((int)start_x,(int)mouse_start_point.getY());
				double xmax = Math.min(end_x, data_area.getMaxX());
				zoom_rectangle = new Rectangle2D.Double(start_x,data_area.getMinY(),xmax - start_x, data_area.getHeight());

				// draw new rectangle
				g2.draw(zoom_rectangle);
				g2.dispose();          
			}            
		}
	}        

	public void mouseReleased(MouseEvent e) {    
		if( MouseUtils.isPopupTrigger(e) ) {        
			// clear all
			if( zoom_rectangle!=null ) {
				Graphics2D g2 = (Graphics2D) getGraphics();
				g2.setXORMode(java.awt.Color.gray);
				g2.draw(zoom_rectangle);
				g2.dispose();
			}
			mouse_start_point = null;
			zoom_rectangle = null;

			// open popup
			current_peak = findPeakAt(e.getPoint());
			enforceSelection(current_peak);        
			createPopupMenu(current_peak!=null).show(theChartPanel, e.getX(), e.getY()); 
		}
		else {
			if( zoom_rectangle!=null && mouse_start_point!=null ) {
				if( Math.abs(e.getX() - mouse_start_point.getX()) > 10 ) {
					//if( e.getX() < mouse_start_point.getX() ) {
					// unzoom all
					//    onZoomNone();
					//}
					//else {        

					// zoom area           
					double start_x = Math.min(e.getX(),mouse_start_point.getX());
					double end_x = Math.max(e.getX(),mouse_start_point.getX());

					Rectangle2D data_area = theChartPanel.getScreenDataArea((int)start_x,(int)mouse_start_point.getY());

					double new_lower_bound = screenToDataCoordX(start_x);
					double new_upper_bound = screenToDataCoordX(Math.min(end_x, data_area.getMaxX()));
					thePlot.getDomainAxis().setRange(new Range(new_lower_bound,new_upper_bound));                    
				}
				else {
					// clear rectangle
					Graphics2D g2 = (Graphics2D) getGraphics();
					g2.setXORMode(java.awt.Color.gray);
					g2.draw(zoom_rectangle);
					g2.dispose();
				}
			}

			// restore zooming
			if( !was_moving && is_moving )
				onActivateZooming();

			zoom_rectangle = null;
			mouse_start_point = null;
		}
	}

	public void mouseClicked(MouseEvent e) {      

		// find peak under mouse
		current_peak = findPeakAt(e.getPoint());

		if( MouseUtils.isSelectTrigger(e) ) 
			setSelection(current_peak);
		else if( MouseUtils.isAddSelectTrigger(e) ) 
			addSelection(current_peak);
		else if( MouseUtils.isActionTrigger(e) ) {
			setSelection(current_peak);        
			onAnnotatePeaks(null);
		}
		else if( MouseUtils.isCtrlActionTrigger(e) ) {
			setSelection(current_peak);        
			onAddPeaks();
		}
	}           

	public void documentChanged(BaseDocument.DocumentChangeEvent e) {
		if( e.getSource()==theWorkspace.getSearchResults() ) {
			if( update_isotope_curves )
				updateIsotopeCurves();
		}
		else {
			removeIsotopeCurves();
			super.documentChanged(e);
		}
	}

	public void internalDocumentChanged(GlycanWorkspace.Event e) {    
		updateMsLevel();
	}

	public void componentResized(ComponentEvent e) {
		if( !checkForUpdates() )
			updateChart();
	}

	public void componentShown(ComponentEvent e) {
		if( !checkForUpdates() )
			updateChart();
	}



}
